#include "Emulator.h"
#include "CPUOpCodes.h"
#include "Log.h"

#include <thread>
#include <chrono>

PNE_NAMESPACE_BEGIN

Emulator::Emulator() : m_cpu(m_bus),
                       m_ppu(m_pictureBus, m_emulatorScreen),
                       m_screenScale(3.f),
                       m_cycleTimer(),
                       m_cpuCycleDuration(std::chrono::nanoseconds(559))
{
    if (!m_bus.SetReadCallback(IORegisters::PPUSTATUS, [&](void)
                               { return m_ppu.GetStatus(); }) ||
        !m_bus.SetReadCallback(IORegisters::PPUDATA, [&](void)
                               { return m_ppu.GetData(); }) ||
        !m_bus.SetReadCallback(IORegisters::JOY1, [&](void)
                               { return m_controller1.Read(); }) ||
        !m_bus.SetReadCallback(IORegisters::JOY2, [&](void)
                               { return m_controller2.Read(); }) ||
        !m_bus.SetReadCallback(IORegisters::OAMDATA, [&](void)
                               { return m_ppu.GetOAMData(); }))
    {
        LOG(Level::Error) << "Critical error: Failed to set I/O callbacks" << std::endl;
    }

    if (!m_bus.SetWriteCallback(IORegisters::PPUCTRL, [&](Byte b)
                                { m_ppu.Control(b); }) ||
        !m_bus.SetWriteCallback(IORegisters::PPUMASK, [&](Byte b)
                                { m_ppu.SetMask(b); }) ||
        !m_bus.SetWriteCallback(IORegisters::OAMADDR, [&](Byte b)
                                { m_ppu.SetOAMAddress(b); }) ||
        !m_bus.SetWriteCallback(IORegisters::PPUADDR, [&](Byte b)
                                { m_ppu.SetDataAddress(b); }) ||
        !m_bus.SetWriteCallback(IORegisters::PPUSCROL, [&](Byte b)
                                { m_ppu.SetScroll(b); }) ||
        !m_bus.SetWriteCallback(IORegisters::PPUDATA, [&](Byte b)
                                { m_ppu.SetData(b); }) ||
        !m_bus.SetWriteCallback(IORegisters::OAMDMA, [&](Byte b)
                                { DMA(b); }) ||
        !m_bus.SetWriteCallback(IORegisters::JOY1, [&](Byte b)
                                {m_controller1.Strobe(b); m_controller2.Strobe(b); }) ||
        !m_bus.SetWriteCallback(IORegisters::OAMDATA, [&](Byte b)
                                { m_ppu.SetOAMData(b); }))
    {
        LOG(Level::Error) << "Critical error: Failed to set I/O callbacks" << std::endl;
    }

    m_ppu.SetInterruptCallback([&]()
                               { m_cpu.Interrupt(InterruptType::NMI); });
}

void Emulator::Run(std::string rom_path)
{
    // 加载rom不成功
    if (!m_cartridge.LoadFromFile(rom_path))
        return;
    m_mapper = Mapper::CreateMapper(
        static_cast<Mapper::Type>(m_cartridge.GetMapper()),
        m_cartridge,
        // callback HERE
        [&]()
        { m_cpu.Interrupt(InterruptType::IRQ); },
        [&]()
        { m_pictureBus.UpdateMirroring(); });

    if (!m_mapper)
    {
        LOG(Level::Error) << "Creating Mapper failed. Probably unsupported." << std::endl;
        return;
    }

    if (!m_bus.SetMapper(m_mapper.get()) ||
        !m_pictureBus.SetMapper(m_mapper.get()))
        return;

    m_cpu.Reset();
    m_ppu.Reset();

    // GUI things
    m_window.create(sf::VideoMode(NESVideoWidth * m_screenScale, NESVideoHeight * m_screenScale),
                    MACRO_STR(PROJECT_NAME), sf::Style::Titlebar | sf::Style::Close | sf::Style::Resize);

    m_window.setVerticalSyncEnabled(true);
    m_emulatorScreen.Create(NESVideoWidth, NESVideoHeight, m_screenScale,
                            sf::Color::White); // define color

    m_cycleTimer = std::chrono::high_resolution_clock::now();
    m_elapsedTime = m_cycleTimer - m_cycleTimer;

    sf::Event event;
    bool focus = true, pause = false;
    while (m_window.isOpen())
    {
        while (m_window.pollEvent(event))
        {
            // maybe switch case better
            if (event.type == sf::Event::Closed ||
                (event.type == sf::Event::KeyPressed &&
                 event.key.code == sf::Keyboard::Escape))
            {
                LOG(Level::Info) << "Window quit now" << std::endl;
                m_window.close();
                return;
            }
            else if (event.type == sf::Event::GainedFocus)
            {
                focus = true;
                m_cycleTimer = std::chrono::high_resolution_clock::now();
            }
            else if (event.type == sf::Event::LostFocus)
            {
                focus = false;
            }
            else if (event.type == sf::Event::KeyPressed &&
                     event.key.code == sf::Keyboard::F2 // F2键
            )
            {
                pause = !pause;
                if (!pause)
                {
                    m_cycleTimer = std::chrono::high_resolution_clock::now();
                    LOG(Level::Info) << "Paused." << std::endl;
                }
                else
                {
                    LOG(Level::Info) << "Unpaused." << std::endl;
                }
            }
            else if (pause &&
                     event.type == sf::Event::KeyReleased &&
                     event.key.code == sf::Keyboard::F3 // F3键
            )
            {
                for (int i{0}; i < 29781; ++i)
                {
                    m_ppu.Step();
                    m_ppu.Step();
                    m_ppu.Step();

                    m_cpu.Step();
                }
            }
            else if (focus &&
                     event.type == sf::Event::KeyReleased &&
                     event.key.code == sf::Keyboard::F4 // F4
            )
            {
                Log::GetInstance().SetLevel(Level::Info);
            }
            else if (focus &&
                     event.type == sf::Event::KeyReleased &&
                     event.key.code == sf::Keyboard::F5 // F5
            )
            {
                Log::GetInstance().SetLevel(Level::InfoVerbose);
            }
        } // while end

        if (focus && !pause)
        {
            m_elapsedTime += std::chrono::high_resolution_clock::now() - m_cycleTimer;
            m_cycleTimer = std::chrono::high_resolution_clock::now();

            while (m_elapsedTime > m_cpuCycleDuration)
            {
                m_ppu.Step();
                m_ppu.Step();
                m_ppu.Step();

                m_cpu.Step();

                m_elapsedTime -= m_cpuCycleDuration;
            }

            m_window.draw(m_emulatorScreen);
            m_window.display();
        }
        else
        {
            sf::sleep(sf::milliseconds(1000 / 60));
        }
    }
}

void Emulator::DMA(Byte page)
{
    m_cpu.SkipDMACycles();
    auto page_ptr = m_bus.GetPagePtr(page);
    if (page_ptr != nullptr)
        m_ppu.DoDMA(page_ptr);
    else
        LOG(Level::Error) << "Can't get pageptr for DMA" << std::endl;
}

void Emulator::SetVideoHeight(int height)
{
    m_screenScale = height / (float)NESVideoHeight;
    LOG(Level::Info) << "Scale: " << m_screenScale << " set. Screen: "
                     << int(NESVideoWidth * m_screenScale) << "x"
                     << int(NESVideoHeight * m_screenScale) << std::endl;
}

void Emulator::SetVideoWidth(int width)
{
    m_screenScale = width / (float)NESVideoWidth;
    LOG(Level::Info) << "Scale: " << m_screenScale << " set. Screen: "
                     << int(NESVideoWidth * m_screenScale) << "x" << int(NESVideoHeight * m_screenScale) << std::endl;
}

void Emulator::SetVideoScale(float scale)
{
    m_screenScale = scale;
    LOG(Level::Info) << "Scale: " << m_screenScale << "set. Screen: "
                     << int(NESVideoWidth * m_screenScale) << "x" << int(NESVideoHeight * m_screenScale) << std::endl;
}

void Emulator::SetKeys(
    std::vector<sf::Keyboard::Key> &p1,
    std::vector<sf::Keyboard::Key> &p2)
{
    m_controller1.SetKeyBind(p1);
    m_controller2.SetKeyBind(p2);
}

PNE_NAMESPACE_END
// DONE